package com.aug_leo.datastructure.blockingqueue;

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 双锁实现
 *
 * @param <E> 元素类型
 */
@SuppressWarnings("all")
public class BlockingQueue2<E> implements BlockingQueue<E> {

    private final E[] array;
    private int head;
    private int tail;

    private AtomicInteger size = new AtomicInteger();

    private ReentrantLock tailLock = new ReentrantLock();
    private Condition tailWaits = tailLock.newCondition();

    private ReentrantLock headLock = new ReentrantLock();
    private Condition headWaits = headLock.newCondition();

    public BlockingQueue2(int capacity) {
        this.array = (E[]) new Object[capacity];
    }

    /**
     * 将指定元素添加到此队列的尾部。
     * 如果在添加元素时检测到当前线程被中断，则抛出InterruptedException异常。
     *
     * @param e 要添加到队列的元素
     * @throws InterruptedException 如果当前线程在等待添加元素时被中断
     */
    @Override
    public void offer(E e) throws InterruptedException {
        int c; // 添加前元素个数
        tailLock.lockInterruptibly();
        try {
            // 1.队列满则等待
            while (isFull()) {
                tailWaits.await(); // offer1 offer2
            }
            // 2.队列不满则入队
            array[tail] = e;
            if (++tail == array.length) {
                tail = 0;
            }

            // 3.修改 size
            c = size.getAndIncrement(); // size++
            if (c + 1 < array.length) {
                tailWaits.signal();
            }
            /*
                1. 读取 成员变量size的值
                2. 自增
                3. 结果写回成员变量size
             */

        } finally {
            tailLock.unlock();
        }
        // 4. 唤醒等待非空的 poll 线程
        //                  0->1    1->2    2->3
        if (c == 0) {
            headLock.lock(); // offer_1 offer_2 offer_3
            try {
                headWaits.signal();
            } finally {
                headLock.unlock();
            }
        }

    }

    /**
     * 将指定元素添加到此队列的尾部，如果在给定的等待时间内队列未满则成功。
     * 如果在添加元素前检测到当前线程被中断，或者在等待超时后未能添加元素，则抛出InterruptedException异常。
     *
     * @param e       要添加到队列的元素
     * @param timeout 等待添加元素的时间，以毫秒为单位
     * @throws InterruptedException 如果当前线程在等待添加元素时被中断或超时
     */
    @Override
    public boolean offer(E e, long timeout) throws InterruptedException {
        return false;
    }

    /**
     * 从此队列的头部移除并返回一个元素。
     * 如果在移除元素时检测到当前线程被中断，则抛出InterruptedException异常。
     *
     * @return 从此队列的头部移除的元素
     * @throws InterruptedException 如果当前线程在等待移除元素时被中断
     */
    @Override
    public E poll() throws InterruptedException {
        E e;
        int c; // 取走前的元素个数
        headLock.lockInterruptibly();
        try {
            // 1.队列空则等待
            while (isEmpty()) {
                headWaits.await(); // poll_1 poll_2 poll_3
            }
            // 2.非空则出队
            e = array[head];
            array[head] = null; // help GC
            if (++head == array.length) {
                head = 0;
            }
            // 3.修改 size
            c = size.getAndDecrement(); // size--
            // 3->2    2->1     1->0
            // poll_1  poll_2   poll_3
            if (c > 1) {
                headWaits.signal();
            }
            /*
                1. 读取 成员变量size的值
                2. 自减
                3. 结果写回成员变量size
             */

        } finally {
            headLock.unlock();
        }

        // 4. 唤醒等待不满的 offer 线程
        // 队列从满->不满时，由poll唤醒等待不满的 offer 线程
        if (c == array.length) {
            tailLock.lock();
            try {
                tailWaits.signal();
            } finally {
                tailLock.unlock();
            }
        }

        return e;
    }

    /**
     * 检查队列是否为空
     *
     * @return 空返回 true, 否则返回 false
     */
    private boolean isEmpty() {
        return size.get() == 0;
    }

    /**
     * 检查队列是否已满
     *
     * @return 满返回 true, 否则返回 false
     */
    private boolean isFull() {
        return size.get() == array.length;
    }

    @Override
    public String toString() {
        return Arrays.toString(array);
    }

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue2<String> queue = new BlockingQueue2<>(3);
        queue.offer("任务1");

        new Thread(() -> {

            try {
                queue.offer("任务2");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }, "offer").start();

        new Thread(() -> {
            try {
                System.out.println(queue.poll());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }

}
